package org.windowkit.toolkit.frame;

import java.awt.Component;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.ArrayList;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.SwingUtilities;

/**
 * This glass pane will set itself to the size of the given frame upon display.
 *
 * Events are forwarded to components by using the method addEventForward.
 * Currently only buttons are supported as other components require somewhat
 * complex logic to detect hits on and are not normally used while glass pane is
 * present.
 *
 * Since this class is simply a transparent panel which sizes itself upon being
 * made visible it is possible to draw or place visible components on it just
 * like a normal panel.
 *
 * @author Eric Lundin
 *
 */
public class GlassPane extends JPanel implements MouseListener, KeyListener,
		MouseMotionListener {
	private static final long serialVersionUID = 8354145622313222166L;

	/**
	 * Keeps track of all components we are forwarding events to.
	 */
	private final ArrayList<JButton> componentList;

	/**
	 * Create a new glass pane.
	 */
	public GlassPane() {
		super();
		componentList = new ArrayList<JButton>();
		setCursor(new Cursor(Cursor.WAIT_CURSOR));
		setOpaque(false);
		addListeners();
		setFocusCycleRoot(true);
		setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
	}

	public void addCenterComponent(final JComponent component) {
		add(Box.createVerticalGlue());
		add(component);
		component.setAlignmentX(.5f);
		add(Box.createVerticalGlue());
	}

	/**
	 * Adds the required listeners.
	 *
	 */
	protected void addListeners() {
		addMouseListener(this);
	}

	/**
	 * Sets whether or not this glass pane should display the hourglass cursor
	 * while active.
	 *
	 * @param useWaitCursor
	 *            True to use the wait cursor. False otherwise.
	 */
	public void setWaitCursorEnabled(final boolean useWaitCursor) {
		if (useWaitCursor) {
			setCursor(Cursor.getPredefinedCursor((Cursor.WAIT_CURSOR)));
		}
		else {
			setCursor(Cursor.getDefaultCursor());
		}
	}

	/**
	 * Set this component to be visible.
	 *
	 * @param visible
	 *            True if the glass pane should intercept events. False
	 *            otherwise.
	 */
	@Override
	public void setVisible(final boolean visible) {
		if (visible) {
			Container container = getParent();
			if (container != null) {
				setPreferredSize(container.getSize());
			}
		}
		super.setVisible(visible);
	}

	/**
	 * Add a button to forward clicks to.
	 *
	 * @param button
	 *            The button to forward clicks to.
	 */
	public void addEventForward(final JButton button) {
		componentList.add(button);
	}

	/**
	 * Determine if the user has clicked the mouse on the glass pane over a
	 * button which we are forwarding events to. If so, call the button's
	 * doClick method to forward the click. This will result in all the button's
	 * ActionListeners being called as well as the click animation being
	 * performed to alert the user that they clicked the button sucessfully.
	 *
	 * @param e
	 *            The mouse event which occured on this glass pane.
	 */
	protected void forwardEvent(final MouseEvent e) {
		Point point;
		for (JButton currentComponent : componentList) {
			point = getConvertedPoint(e, currentComponent);
			if (currentComponent.contains(point)
					&& !pointIntercepted(e.getPoint())) {
				currentComponent.doClick();
			}
		}
	}

	/**
	 * If any opaque components which are on this glass pane are hit by a mouse
	 * click we do not want to send events to the component covered by them.
	 *
	 * @param p
	 *            The point where the mouse click occured.
	 * @return True if this click hits a component on the glass pane. False
	 *         otherwise.
	 */
	protected boolean pointIntercepted(final Point p) {
		boolean intercepts = false;
		for (Component currentComponent : getComponents()) {
			if (currentComponent.contains(p) && currentComponent.isOpaque()) {
				intercepts = true;
				break;
			}
		}
		return intercepts;
	}

	/**
	 * Converts a point from this glass pane's coordinate space to the given
	 * component's coordinate space.
	 *
	 * @param e
	 *            The MouseEvent which occurred on the source.
	 * @param component
	 *            The component which we may have hit.
	 * @return The event source's coordinates translated to the component's
	 *         coordinate space.
	 */
	private Point getConvertedPoint(final MouseEvent e, final JComponent component) {
		return SwingUtilities.convertPoint(this.getParent(), e.getPoint(),
				component);
	}

	/**
	 * {@inheritDoc}
	 */
	public void mouseClicked(final MouseEvent e) {
		forwardEvent(e);
	}

	/**
	 * {@inheritDoc}
	 */
	public void mousePressed(final MouseEvent e) {
	}

	/**
	 * {@inheritDoc}
	 */
	public void mouseReleased(final MouseEvent e) {
	}

	/**
	 * {@inheritDoc}
	 */
	public void mouseEntered(final MouseEvent e) {
	}

	/**
	 * {@inheritDoc}
	 */
	public void mouseExited(final MouseEvent e) {
	}

	/**
	 * {@inheritDoc}
	 */
	public void keyTyped(final KeyEvent e) {
		e.consume();
	}

	/**
	 * {@inheritDoc}
	 */
	public void keyPressed(final KeyEvent e) {
		e.consume();
	}

	/**
	 * {@inheritDoc}
	 */
	public void keyReleased(final KeyEvent e) {
		e.consume();
	}

	/**
	 * {@inheritDoc}
	 */
	public void mouseDragged(final MouseEvent e) {
		e.consume();
	}

	/**
	 * {@inheritDoc}
	 */
	public void mouseMoved(final MouseEvent e) {
		e.consume();
	}

	public static class LoadingPane extends JPanel {
		private static final long serialVersionUID = 3402767933083468554L;

		public LoadingPane() {
			this(new Dimension(250, 100), "Loading...");
		}

		public LoadingPane(final Dimension size, final String msg) {
			setMaximumSize(size);
			setPreferredSize(size);
			setBorder(BorderFactory.createCompoundBorder(
					BorderFactory.createRaisedBevelBorder(),
					BorderFactory.createEmptyBorder(5, 5, 5, 5)));
			setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
			add(Box.createVerticalStrut(15));
			add(new JLabel(msg));
			add(Box.createVerticalStrut(10));
			JProgressBar bar = new JProgressBar();
			bar.setIndeterminate(true);
			add(bar);
			add(Box.createVerticalGlue());
		}
	}
}
